#include "udp_common.hpp"
#include <thread>
#include <sstream>

// class UdpClient {
// public:
//     UdpClient(std::string ip, int port) : ip_(ip), port_(port) {
//         sock_ = socket(AF_INET, SOCK_DGRAM, 0);
//         if (sock_ < 0) { perror("socket"); exit(1); }

//         // 目标地址
//         peer_.sin_family = AF_INET;
//         peer_.sin_port   = htons(port_);
//         inet_pton(AF_INET, ip_.c_str(), &peer_.sin_addr);
//     }

//     void run() {
//         std::cout << "[UDP Client] send to " << ip_ << ":" << port_
//                   << ", type /quit to exit\n";

//         // 接收线程
//         std::thread recv_thr(&UdpClient::recvLoop, this);

//         // 主线程：键盘输入 -> 发送
//         std::string line;
//         while (std::getline(std::cin, line)) {
//             if (line == "/quit") break;
//             sendto(sock_, line.c_str(), line.size(), 0,
//                    (sockaddr*)&peer_, sizeof(peer_));
//         }
//         close(sock_);
//         recv_thr.join();
//     }
//    // 客户端
// void sendWithAck(int sock, const sockaddr_in& peer, const std::string& message) {
//     const int maxRetries = 3;
//     const int timeout = 1000; // 1秒超时
//     struct timeval tv;
//     tv.tv_sec = timeout / 1000;
//     tv.tv_usec = (timeout % 1000) * 1000;

//     for (int retry = 0; retry < maxRetries; ++retry) {
//         sendto(sock, message.c_str(), message.size(), 0, (sockaddr*)&peer, sizeof(peer));
//         fd_set readfds;
//         FD_ZERO(&readfds);
//         FD_SET(sock, &readfds);
//         int result = select(sock + 1, &readfds, NULL, NULL, &tv);
//         if (result > 0) {
//             char buf[BUF_SIZE];
//             socklen_t len = sizeof(peer);
//             recvfrom(sock, buf, BUF_SIZE, 0, (sockaddr*)&peer, &len);
//             std::cout << "[Client] Received ACK from server\n";
//             return;
//         } else {
//             std::cout << "[Client] Timeout, retrying...\n";
//         }
//     }
//     std::cout << "[Client] Failed to send message after " << maxRetries << " retries\n";
// }
// void sendMessage(int sock, const sockaddr_in& peer, const Message& msg) {
//     char buf[BUF_SIZE];
//     std::snprintf(buf, BUF_SIZE, "%d:%s", msg.seqNum, msg.content.c_str());
//     sendto(sock, buf, strlen(buf), 0, (sockaddr*)&peer, sizeof(peer));
// }

// private:
//     void recvLoop() {
//         char buf[BUF_SIZE];
//         while (true) {
//             ssize_t n = recv(sock_, buf, BUF_SIZE - 1, 0);
//             if (n <= 0) break;
//             buf[n] = '\0';
//             std::cout << "[UDP Client] server echo: " << buf << '\n';
//         }
//     }

//     int          sock_;
//     std::string  ip_;
//     int          port_;
//     sockaddr_in  peer_;
// };

// 解决UDP的数据包丢失
// 1 在每个数据包中添加序列号。
// 2 发送数据包后等待服务器的确认（ACK）。
// 3 如果在超时时间内没有收到确认，重新发送数据包。
struct Message
{
    int seqNum; // 序列号
    std::string content;
};
class UdpClient
{
public:
    UdpClient(std::string ip, int port) : ip_(ip), port_(port), seqNum_(0)
    {
        sock_ = socket(AF_INET, SOCK_DGRAM, 0);
        if (sock_ < 0)
        {
            perror("socket");
            exit(1);
        }

        // 目标地址
        peer_.sin_family = AF_INET;
        peer_.sin_port = htons(port_);
        inet_pton(AF_INET, ip_.c_str(), &peer_.sin_addr);
    }

    void run()
    {
        std::cout << "[UDP Client] send to " << ip_ << ":" << port_
                  << ", type /quit to exit\n";

        // 接收线程
        std::thread recv_thr(&UdpClient::recvLoop, this);

        // 主线程：键盘输入 -> 发送
        std::string line;
        while (std::getline(std::cin, line))
        {
            if (line == "/quit")
                break;
            Message msg{seqNum_++, line};
            sendWithAck(msg);
        }
        close(sock_);
        recv_thr.join();
    }

private:
    void recvLoop()
    {
        char buf[BUF_SIZE];
        while (true)
        {
            ssize_t n = recv(sock_, buf, BUF_SIZE - 1, 0);
            if (n <= 0)
                break;
            buf[n] = '\0';
            std::cout << "[UDP Client] server echo: " << buf << '\n';
        }
    }

    void sendWithAck(const Message &msg)
    {
        const int maxRetries = 3;
        const int timeout = 1000; // 1秒超时
        struct timeval tv;
        tv.tv_sec = timeout / 1000;
        tv.tv_usec = (timeout % 1000) * 1000;

        std::string message;
        {                                            // 作用域：限制 oss 的生命周期，避免不必要的内存占用
            std::ostringstream oss;                  // 创建一个字符串输出流对象
            oss << msg.seqNum << ":" << msg.content; // 向流中写入数据
            message = oss.str();                     // 提取流中的内容，转换为 std::string
        }

        for (int retry = 0; retry < maxRetries; ++retry)
        {
            // 1. 发送消息到服务器
            sendto(
                sock_,              // UDP套接字
                message.c_str(),    // 要发送的消息内容
                message.size(),     // 消息长度
                0,                  // 标志位（0表示默认）
                (sockaddr *)&peer_, // 服务器地址（IP+端口）
                sizeof(peer_)       // 地址结构体大小
            );
            // 准备等待服务器回应（使用select实现超时等待）
            fd_set readfds;          // 文件描述符集合（用于监听可读事件）
            FD_ZERO(&readfds);       // 清空集合
            FD_SET(sock_, &readfds); // 将UDP套接字加入集合（监听其是否有数据到来）
            // 3. 调用select等待数据，超时时间由tv指定
            int result = select(
                sock_ + 1,  // 监听的最大文件描述符+1
                &readfds,   // 监听可读事件的集合
                NULL, NULL, // 不监听可写和异常事件
                &tv         // 超时时间（超过这个时间没收到数据就触发超时）
            );
            // 4. 根据select的结果处理
            if (result > 0) // 有数据到来（收到服务器的ACK）
            {
                char buf[BUF_SIZE]; // 存储接收的ACK数据
                socklen_t len = sizeof(peer_);
                // 接收服务器回应
                recvfrom(
                    sock_,              // UDP套接字
                    buf,                // 接收缓冲区
                    BUF_SIZE,           // 缓冲区大小
                    0,                  // 标志位
                    (sockaddr *)&peer_, // 用于获取发送方（服务器）的地址
                    &len                // 地址结构体大小（传入传出参数）
                );
                std::cout << "[Client] Received ACK from server\n";
                return; // 收到ACK，成功退出
            }
            else // 超时（result=0）或出错（result=-1），重试
            {
                std::cout << "[Client] Timeout, retrying...\n";
            }
        }
        std::cout << "[Client] Failed to send message after " << maxRetries << " retries\n";
    }

    int sock_;
    std::string ip_;
    int port_;
    sockaddr_in peer_;
    int seqNum_; // 序列号
};

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: ./udp_client <server_ip> <port>\n";
        return 1;
    }

    // 客户端自己的ip和port为什么不需要传入参数，客户端自己不需要ip和端口号吗?
    // 客户端当然需要自己的 IP 和端口号,IP 由操作系统自动绑定,端口由操作系统自动分配
    // 唯一性由五元组保证
    // 数据包实际上带着五元组：
    //(源IP, 源端口, 目的IP, 目的端口, 协议)
    // 即使多台客户端都用同一个临时端口，只要 源 IP 不同（或者 IP 相同但端口不同），服务器也能区分。

    // ip 和 port 是目标主机的地址
    UdpClient cli(argv[1], std::stoi(argv[2]));
    cli.run();
    return 0;
}
